#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <list>

using namespace std;


struct client_info
{
	char ip[16];
	unsigned short port;
	int sock;
};


list<client_info*> clients;
pthread_rwlock_t rwlock;


void* comm_thr(void* arg);


int main(int argc, char** argv)
{
	signal(SIGPIPE, SIG_IGN);

	pthread_rwlock_init(&rwlock, NULL);

	/*
	if(argc != 2)
	{
		fprintf(stderr, "Usage error!\n");
		return 1;
	}
	*/


	// 第 1 步：创建一个监听套接字
	int sock_listen = socket(AF_INET, SOCK_STREAM, 0);
	/*
	socket 函数的参数解读：
	第一个参数表示地址家族，基本上都是 AF_INET，表示使用英特网地址家族；
	第二个参数表示套接字类型，通常有两种，即 SOCK_STREAM 和 SOCK_DGRAM，前者表示流式套接字，用于基于 TCP 协议的通信，后者表示数据报式套接字，用于基于 UDP 协议的通信；
	第三个参数表示使用的通信协议编号，0 表示使用默认协议。

	socket 函数的返回值为新套接字的描述符，如果失败，返回值为 -1
	*/

	if(sock_listen == -1)
	{
		perror("socket");
		return 1;
	}


	// 设置套接字属性，以允许地址复用
	int val = 1;
	setsockopt(sock_listen, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));


	// 第 2 步：绑定地址（IP + Port）
	struct sockaddr_in myaddr;
	myaddr.sin_family = AF_INET;  // 指定地址家族
	myaddr.sin_addr.s_addr = INADDR_ANY;  // 指定使用本机任意地址(0.0.0.0)
	//myaddr.sin_addr.s_addr = inet_addr("192.168.0.239");  // 指定使用本机某个具体的 IP 地址
	myaddr.sin_port = htons(9999);  // 指定使用的端口号为 6666

	if(-1 == bind(sock_listen, (struct sockaddr*)&myaddr, sizeof(myaddr)))
	{
		perror("bind");
		return 1;
	}
	
	/*
	inet_addr 函数的作用：将字符串形式的 IP 地址转换为无符号 32 位整数形式（网络字节序）。
	htons 函数的作用：将某个短整数(short)从主机(host)字节序转换为网络(network)字节序。
	*/

	
	// 第 3 步：监听
	if(-1 == listen(sock_listen, 5))
	{
		perror("listen");
		return 1;
	}

	/*
	listen 函数的第二个参数表示连接等待队列的长度。
	*/

	daemon(1, 1);

	pthread_t tid;
	struct client_info* ci = NULL;

	while(1)
	{
		// 第 4 步：接收客户端连接请求
		//int sock_conn = accept(sock_listen, NULL, NULL);
		struct sockaddr_in client_addr;
		socklen_t addr_len = sizeof(client_addr);
		int sock_conn = accept(sock_listen, (struct sockaddr*)&client_addr, &addr_len);

		if(sock_conn == -1)
		{
			perror("accept");
			return 1;
		}

		/*
		   accept 函数参数解读：
		   第一个参数为监听套接字描述符；
		   第二个参数为地址结构体(struct sockaddr_in)指针，用于接收客户端的地址信息，如果对客户端地址不感兴趣，就传 NULL；
		   第三个参数为地址结构体长度，用于接收客户端地址长度，如果对客户端地址不感兴趣，就传 NULL。

		   如果成功，accept 函数的返回值为这条连接对应的套接字（通常称为连接套接字），后面使用这个套接字进行收发数据。如果失败，其返回值为 -1。

		   调用 accept 函数时，如果没有任何客户端连接请求到来，该函数会阻塞调用线程直到成功接收到一个客户端连接请求或出现错误才返回。
		   */

		ci = (struct client_info*)malloc(sizeof(struct client_info));

		if(ci == NULL)
		{
			perror("malloc");
			exit(1);
		}

		strcpy(ci->ip, inet_ntoa(client_addr.sin_addr));
		ci->port = ntohs(client_addr.sin_port);
		ci->sock = sock_conn;

		printf("\n客户端(%s:%d)已连接！\n", ci->ip, ci->port);

		if(pthread_create(&tid, NULL, comm_thr, ci))
		{
			perror("pthread_create");
			printf("\n客户端(%s:%d)已断开连接！\n", ci->ip, ci->port);
			close(sock_conn);
			free(ci);
			continue;
		}

		pthread_rwlock_wrlock(&rwlock);
		clients.push_back(ci);
		pthread_rwlock_unlock(&rwlock);
	}


	// 第 7 步：关闭监听套接字（如果不需要继续跟其他客户端通信的话）
	close(sock_listen);

	return 0;
}


void* comm_thr(void* arg)
{
	struct client_info* ci = (struct client_info*)arg;
	char buff[1025];
	int ret;

	pthread_detach(pthread_self());

	// 第 5 步：收发数据
	while((ret = recv(ci->sock, buff, sizeof(buff), 0)) > 0)
	{
		pthread_rwlock_rdlock(&rwlock);

		for(auto it = clients.begin(); it != clients.end(); ++it)
		{
			if(*it != ci)
				send((*it)->sock, buff, ret, 0);
		}

		pthread_rwlock_unlock(&rwlock);
	}

	
	// 第 6 步：断开连接（如果不需要继续和当前连接的客户端通信的话）
	pthread_rwlock_wrlock(&rwlock);
	clients.remove(ci);
	pthread_rwlock_unlock(&rwlock);

	close(ci->sock);

	printf("\n客户端(%s:%d)已断开连接！\n", ci->ip, ci->port);

	free(ci);

	pthread_exit(NULL);
}
